﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Timers;
using FNN.CustomFunctions;
using FNN.Networks;
using FNN.SourcePoints;
using System.Threading.Tasks;
using FNN.Genetic.Managers;
using FNN.Genetic.Mutations;
using FNN.Genetic;
using FNN.Intervals;

namespace FNN.NetworkMasters
{
    public class RegularNetworkMaster : NetworkMaster
    {
        protected List<SourcePoint> DataSourceValue;
        protected double NeededErrorRateValue = -1.0;

        public event IterationChangedContainer IterationChanged;
        public event IterationChangedContainer GenerationChanged;
        public event IterationResultsImprovedContainer IterationResultsImproved;
        public event IterationResultsImprovedContainer GenerationResultsImproved;

        /// <summary>
        ///     Необходимый уровень ошибки
        /// </summary>
        public double NeededErrorRate
        {
            get { return NeededErrorRateValue; }
            set { NeededErrorRateValue = value; }
        }

        /// <summary>
        ///     Исходные данные
        /// </summary>
        public List<SourcePoint> DataSource
        {
            get { return DataSourceValue; }
            set { DataSourceValue = value; }
        }

        /// <summary>
        ///     Пройти эпоху обучения сети
        /// </summary>
        /// <param name="target">Целевая сеть</param>
        /// <param name="isMultiNetwork">Множественность выходов сети</param>
        public override void RunEpoch(ref Network target, bool isMultiNetwork)
        {
            var coordinatesCount = DataSourceValue.First().Coordinates.Count;
            var inputsForCoordinate = target.InputCount / coordinatesCount;

            var usedTarget = (RegularNetwork) target;
            usedTarget.SuccessCount = 0;

            for (var j = 0; j < (DataSourceValue.Count() - (inputsForCoordinate)); j++)
            {
                for (var k = 0; k < inputsForCoordinate; k++)
                {
                    for (var i = 0; i < coordinatesCount; i++)
                    {
                        target.Layers.ElementAt(0).Neurons.ElementAt((k * coordinatesCount) + i).InputValue =
                            DataSourceValue[j + k].Coordinates.Values.ElementAt(i);
                    }
                }

                int result;
                if (isMultiNetwork)
                {
                    var networkResults = target.GetNetworkResults();
                    result = networkResults.IndexOf(networkResults.Max(o => o)) - 1;
                }
                else
                {
                    var networkResult = new Sigmoid().Calculate(usedTarget.GetNetworkResult());
                    var rangeList = new List<ValueInterval>
                    {
                        new ValueInterval {Start = 0, End = 0.33, Value = -1},
                        new ValueInterval {Start = 0.33, End = 0.66, Value = 0},
                        new ValueInterval {Start = 0.66, End = 1, Value = 1}
                    };
                    result = rangeList.First(o => (o.Start <= networkResult) && (o.End >= networkResult)).Value;
                }

                if (result == ((CategorySourcePoint)DataSourceValue[j]).CategoryId)
                {
                    usedTarget.SuccessCount += 1;
                }
            }
        }

        /// <summary>
        ///     Обучить сеть
        /// </summary>
        /// <param name="target">Целевая сеть</param>
        /// <returns>Обученная сеть</returns>
        public override Network TrainNetwork(Network target)
        {
            var usedTarget = (RegularNetwork) target;
            bool needToContinue;
            var timeElapsed = false;
            var iterationCount = 0;

            if (TimeLimitValue != 0)
            {
                var workTimer = new Timer {Interval = (TimeLimitValue*1000*60*60)};
                workTimer.Elapsed += (sender, args) => timeElapsed = true;
                workTimer.Enabled = true;
            }

            double lastErrorRate = 1;
            
            var synapsesChangeCount =
                ((Convert.ToInt32(ConfigurationManager.AppSettings["useDefaultSynapsesChangeCount"]) == 1) &&
                 (usedTarget.Synapses.Count >=
                  Convert.ToInt32(ConfigurationManager.AppSettings["synapsesChangeCount"])))
                    ? Convert.ToInt32(ConfigurationManager.AppSettings["synapsesChangeCount"])
                    : usedTarget.LayerCount;

            do
            {    
                var synapsesChangeDictionary = new Dictionary<int, double>();

                for (int i = 0; i < synapsesChangeCount; i++)
                {
                    var randomChangeIndex = Randomizer.Next(0, target.Synapses.Count);
                    while (synapsesChangeDictionary.ContainsKey(randomChangeIndex))
                    {
                        randomChangeIndex = Randomizer.Next(0, target.Synapses.Count);
                    }
                    synapsesChangeDictionary.Add(randomChangeIndex, 0);
                }

                target.RecalculateWeights(ref synapsesChangeDictionary);                
                RunEpoch(ref target, false);
                iterationCount++;
                if (IterationChanged != null) IterationChanged(iterationCount);

                var currentErrorRate = (DataSource.Count - usedTarget.SuccessCount) / (double)DataSource.Count;
                if (currentErrorRate < lastErrorRate)
                {
                    lastErrorRate = currentErrorRate;
                    if (IterationResultsImproved != null) IterationResultsImproved(iterationCount, lastErrorRate);
                }
                else
                {
                    usedTarget.CancelChange(synapsesChangeDictionary);
                }

                var iterationsCompleted = (IterationsLimitValue != 0) && (iterationCount >= IterationsLimitValue);
                var errorCompleted = (Math.Abs(NeededErrorRateValue - (-1.0)) > 0.005) &&
                                      (currentErrorRate <= NeededErrorRateValue);
                needToContinue = !iterationsCompleted && !errorCompleted;
            } while (needToContinue && (!timeElapsed));

            return usedTarget;
        }

        /// <summary>
        ///     Обучить сеть с множественными выходами
        /// </summary>
        /// <param name="target">Целевая сеть</param>
        /// <returns>Обученная сеть</returns>
        public override Network TrainMultiNetwork(Network target)
        {
            var usedTarget = (RegularNetwork)target;
            bool needToContinue;
            var timeElapsed = false;
            var iterationCount = 0;

            if (TimeLimitValue != 0)
            {
                var workTimer = new Timer() { Interval = (TimeLimitValue * 60 * 60 * 1000) };
                workTimer.Elapsed += (sender, args) => timeElapsed = true;
                workTimer.Enabled = true;
            }

            double bestErrorRate = 1;

            var synapsesChangeCount =
                ((Convert.ToInt32(ConfigurationManager.AppSettings["useDefaultSynapsesChangeCount"]) == 1) &&
                 (usedTarget.Synapses.Count >=
                  Convert.ToInt32(ConfigurationManager.AppSettings["synapsesChangeCount"])))
                    ? Convert.ToInt32(ConfigurationManager.AppSettings["synapsesChangeCount"])
                    : usedTarget.LayerCount;

            do
            {
                var synapsesChangeDictionary = new Dictionary<int, double>();

                for (var i = 0; i < synapsesChangeCount; i++)
                {
                    var randomChangeIndex = Randomizer.Next(0, target.Synapses.Count);
                    while (synapsesChangeDictionary.ContainsKey(randomChangeIndex))
                    {
                        randomChangeIndex = Randomizer.Next(0, target.Synapses.Count);
                    }
                    synapsesChangeDictionary.Add(randomChangeIndex, 0);
                }

                target.RecalculateWeights(ref synapsesChangeDictionary);                
                RunEpoch(ref target, true);
                iterationCount++;
                if (IterationChanged != null) IterationChanged(iterationCount);

                var currentErrorRate = (DataSource.Count - usedTarget.SuccessCount) / (double)DataSource.Count;
                if (currentErrorRate < bestErrorRate)
                {
                    bestErrorRate = currentErrorRate;
                    if (IterationResultsImproved != null) IterationResultsImproved(iterationCount, bestErrorRate);
                }
                else
                {
                    usedTarget.CancelChange(synapsesChangeDictionary);
                }

                var iterationsCompleted = (IterationsLimitValue != 0) && (iterationCount >= IterationsLimitValue);
                var errorCompleted = (Math.Abs(NeededErrorRateValue + (1.0)) > 0.005) &&
                                      (currentErrorRate <= NeededErrorRateValue);
                needToContinue = !iterationsCompleted && !errorCompleted;

            } while (needToContinue && (!timeElapsed));

            return usedTarget;
        }

        /// <summary>
        ///     Подбор топологии сети с помощью генетического алгоритма
        /// </summary>
        /// <param name="progenitor">Предок</param>
        /// <returns>Лучший потомок</returns>
        public override Network NetworkCultivation(Network progenitor, bool isMultiNetwork)
        {
            var usedProgenitor = (RegularNetwork)progenitor;
            bool needToContinue;
            var timeElapsed = false;
            var iterationCount = 0;

            if (GeneticTimeLimitValue != 0)
            {
                var workTimer = new Timer() { Interval = (GeneticTimeLimitValue * 1000 * 60 * 60) };
                workTimer.Elapsed += (sender, args) => timeElapsed = true;
                workTimer.Enabled = true;
            }

            double bestErrorRate = 1;            
            var lastGeneration = CreateGeneration(progenitor, PopulationCountValue);
            var currentGeneration = NetworkCultivationEpoch(lastGeneration, isMultiNetwork);
            lastGeneration = currentGeneration;
            iterationCount++;
            if (GenerationChanged != null) GenerationChanged(iterationCount);

            do
            {
                currentGeneration = NetworkCultivationEpoch(CreateGeneration(lastGeneration, 2), isMultiNetwork);
                lastGeneration = currentGeneration;
                iterationCount++;
                if (GenerationChanged != null) GenerationChanged(iterationCount);

                RegularNetwork bestNetwork = (RegularNetwork)currentGeneration.OrderByDescending(o => ((RegularNetwork)o).SuccessCount).FirstOrDefault();
                var currentErrorRate = (DataSource.Count - bestNetwork.SuccessCount) / (double)DataSource.Count;

                if (currentErrorRate < bestErrorRate)
                {
                    usedProgenitor = bestNetwork;
                    bestErrorRate = currentErrorRate;
                    if (GenerationResultsImproved != null) GenerationResultsImproved(iterationCount, bestErrorRate);
                }

                var iterationsCompleted = (GeneticIterationsLimitValue != 0) && (iterationCount >= GeneticIterationsLimitValue);
                var errorCompleted = (Math.Abs(NeededErrorRateValue + (1.0)) > 0.005) &&
                                      (currentErrorRate <= NeededErrorRateValue);
                needToContinue = !iterationsCompleted && !errorCompleted;

            } while (needToContinue && (!timeElapsed));

            return usedProgenitor;
        }

        /// <summary>
        ///     Пройти эпоху генетического обучения
        /// </summary>
        /// <param name="progenitor">Предок</param>
        /// <param name="isMultiNetwork">Множественность выходов сети</param>
        /// <returns>Лучший потомок</returns>
        public override Network GeneticTrainingEpoch(Network progenitor, bool isMultiNetwork)
        {
            var networksList = new List<RegularNetwork>();
            var randomizer = new Random();
            Parallel.For(0, PopulationCountValue, i =>
            {
                Network newRegularNetwork = new RegularNetwork((RegularNetwork)progenitor);
                var indexDictionary = new Dictionary<int, double>();
                for (var j = 0; j < newRegularNetwork.Synapses.Count * 5 / 10; j++)
                {
                    bool keyExists;
                    int randomValueIndex;
                    do
                    {
                        randomValueIndex = randomizer.Next(0, newRegularNetwork.Synapses.Count);
                        keyExists = indexDictionary.ContainsKey(randomValueIndex);
                    } while (keyExists);

                    indexDictionary.Add(randomValueIndex, 0);
                }
                newRegularNetwork.RecalculateWeights(ref indexDictionary);
                RunEpoch(ref newRegularNetwork, isMultiNetwork);
                networksList.Add((RegularNetwork)newRegularNetwork);
            });

            Network bestNetwork = networksList.FirstOrDefault(o => o.SuccessCount == networksList.Max(p => p.SuccessCount));
            return bestNetwork;
        }

        /// <summary>
        ///     Обучение сети с помощью генетического алгоритма
        /// </summary>
        /// <param name="target">Целевая сеть</param>
        /// <param name="isMultiNetwork">Множественность выходов сети</param>
        /// <returns>Лучший потомок</returns>
        public override Network GeneticNetworkTraining(Network target, bool isMultiNetwork)
        {
            var usedTarget = (RegularNetwork)target;
            bool needToContinue;
            var timeElapsed = false;
            var iterationCount = 0;
            double lastError = 1;

            if (TimeLimitValue != 0)
            {
                var workTimer = new Timer { Interval = (TimeLimitValue * 1000 * 60 * 60) };
                workTimer.Elapsed += (sender, args) => timeElapsed = true;
                workTimer.Enabled = true;
            }

            do
            {
                usedTarget = (RegularNetwork)GeneticTrainingEpoch(usedTarget, isMultiNetwork);
                iterationCount++;
                if (IterationChanged != null) IterationChanged(iterationCount);

                var currentErrorRate = (DataSource.Count - usedTarget.SuccessCount) / (double)DataSource.Count;

                if (currentErrorRate < lastError)
                {
                    lastError = currentErrorRate;
                    if (IterationResultsImproved != null) IterationResultsImproved(iterationCount, currentErrorRate);
                }

                var iterationsCompleted = (IterationsLimitValue != 0) && (iterationCount >= IterationsLimitValue);
                var errorCompleted = (Math.Abs(NeededErrorRateValue + (1.0)) > 0.005) &&
                                      (currentErrorRate <= NeededErrorRateValue);
                needToContinue = !iterationsCompleted && !errorCompleted;
            } while (needToContinue && (!timeElapsed));

            return usedTarget;
        }

        /// <summary>
        ///     Проход эпохи культивирования сети с нужной топологией
        /// </summary>
        /// <param name="targetGeneration">Текущее поколение</param>
        /// <param name="isMultiNetwork">Множественность выходов сети</param>
        /// <returns></returns>
        public override List<Network> NetworkCultivationEpoch(List<Network> targetGeneration, bool isMultiNetwork)
        {
            var bestNetworksList = new List<Network>();
            int bestNetworksCount = Convert.ToInt32(ConfigurationManager.AppSettings["bestNetworksNumber"]);

            Parallel.For(0, targetGeneration.Count(), i =>
            {
                targetGeneration[i] = (isMultiNetwork) ? TrainMultiNetwork(targetGeneration.ElementAt(i)) : TrainNetwork(targetGeneration.ElementAt(i));                
            });

            var sortedNetworks = targetGeneration.OrderBy(o => ((RegularNetwork)o).SuccessCount);
            for (int i = 0; i < bestNetworksCount; i++)
            {
                bestNetworksList.Add(new RegularNetwork((RegularNetwork)sortedNetworks.ElementAt(i)));
            }

            RegularCrossbreedingManager manager = new RegularCrossbreedingManager();
            var result = manager.AdvancedSelection(bestNetworksList);

            return result;
        }

        public override List<Network> CreateGeneration(Network progenitor, int neededPopulationCount)
        {
            var resultList = new List<Network>();
            int maxMutationCount = Convert.ToInt32(ConfigurationManager.AppSettings["maxMutationCount"]);
            var randomizer = new Random();
            
            for (int i = 0; i < neededPopulationCount; i++)
            {
                var networkChild = new RegularNetwork((RegularNetwork)progenitor);
                var mutationCount = randomizer.Next(0, maxMutationCount);

                var networkChildDna = new DnaHelix();
                networkChildDna.ParseNetwork(networkChild);

                for (int k = 0; k < mutationCount; k++)
                {
                    var mutationType = (MutationTypes)randomizer.Next(0, System.Enum.GetNames(typeof(MutationTypes)).Count());
                    var mutationTarget = (MutationTargets)randomizer.Next(0, System.Enum.GetNames(typeof(MutationTargets)).Count());
                    Mutation currentMutation = null;

                    switch (mutationTarget)
                    {
                        case MutationTargets.Layer:
                            currentMutation = new LayerMutation(mutationType) { TargetDna = networkChildDna };
                            break;
                        case MutationTargets.Synapse:
                            currentMutation = new SynapseMutation(mutationType) { TargetDna = networkChildDna };
                            break;
                    }

                    if (currentMutation != null)
                    {
                        var resultDna = currentMutation.TargetDna;
                        networkChild = new RegularNetwork(resultDna.GetNetworkStructure());
                    }
                }

                resultList.Add(networkChild);
            }

            return resultList;
        }

        public override List<Network> CreateGeneration(List<Network> progenitorGeneration, int populationMultiplier)
        {
            var resultList = new List<Network>();
            int maxMutationCount = Convert.ToInt32(ConfigurationManager.AppSettings["maxMutationCount"]);
            var randomizer = new Random();

            for (int j = 0; j < progenitorGeneration.Count; j++)
            {
                for (int i = 0; i < populationMultiplier; i++)
                {
                    var networkChild = new RegularNetwork((RegularNetwork)progenitorGeneration[j]);
                    var mutationCount = randomizer.Next(0, maxMutationCount);

                    var networkChildDna = new DnaHelix();
                    networkChildDna.ParseNetwork(networkChild);

                    for (int k = 0; k < mutationCount; k++)
                    {
                        var mutationType = (MutationTypes)randomizer.Next(0, System.Enum.GetNames(typeof(MutationTypes)).Count());
                        var mutationTarget = (MutationTargets)randomizer.Next(0, System.Enum.GetNames(typeof(MutationTargets)).Count());
                        Mutation currentMutation = null;

                        switch (mutationTarget)
                        {
                            case MutationTargets.Layer:
                                currentMutation = new LayerMutation(mutationType) { TargetDna = networkChildDna };
                                break;
                            case MutationTargets.Synapse:
                                currentMutation = new SynapseMutation(mutationType) { TargetDna = networkChildDna };
                                break;
                        }

                        if (currentMutation != null)
                        {
                            var resultDna = currentMutation.TargetDna;
                            networkChild = new RegularNetwork(resultDna.GetNetworkStructure());
                        }
                    }

                    resultList.Add(networkChild);
                }
            }
            
            return resultList;
        }
    }
}